pragma Ada_2012;

with Ada.Containers; use Ada.Containers;
with Ada.Unchecked_Deallocation;

generic
   type Element_Type is private;
   type Index_Type is (<>);
   type Array_Type is array(Index_Type range <>) of Element_Type;
package Generic_Array is

   type Index_Pair_Type is record
      First, Second : Index_Type;
   end record;

   subtype Empty_Index is Index_Type range Index_Type'Last .. Index_Type'First;

   function Always_True (Item : Element_Type) return Boolean is (True);

   function Always_False (Item : Element_Type) return Boolean is (False);

   function Empty return Array_Type is (Array_Type'(Empty_Index => <>))
      with Pre => Index_Type'Range_Length > 1;

   generic
      Source : in out Array_Type;
   procedure Generic_Swap (I, J : Index_Type) 
      with Pre => I in Source'Range and J in Source'Range;

   generic
      Result : access Index_Type := null;
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Find (
      Source : Array_Type;
      Value : Element_Type
      ) return Boolean;

   generic
      Result : access Index_Type := null;
      with function Predicate (Item : Element_Type) return Boolean;
   function Generic_Find_If (
      Source : Array_Type
      ) return Boolean;

   generic
      Result : access Index_Type := null;
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Search (
      Source : Array_Type;
      Pattern : Array_Type
      ) return Boolean;

   generic
      Result : access Index_Type := null;
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Find_End (
      Source : Array_Type;
      Pattern : Array_Type
      ) return Boolean;


   generic
      Result : access Index_Type := null;
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Find_First_Of (
      Source : Array_Type;
      Any : Array_Type
      ) return Boolean;

   generic
      Result : access Index_Type := null;
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Adjacent_Find (
      Source : Array_Type
      ) return Boolean;

   function Count (
      Source : Array_Type;
      Item : Element_Type
      ) return Count_Type;

   generic
      with function Predicate (Item : Element_Type) return Boolean;
   function Generic_Count_If (
      Source : Array_Type
      ) return Count_Type;
      

   generic
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Mismatch (
      Left : Array_Type;
      Right : Array_Type
      ) return Index_Pair_Type;

   generic
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Equal (
      Left : Array_Type;
      Right : Array_Type
      ) return Boolean;

   generic
      with function ">" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Quick_Select (
      List : in out Array_Type;
      N    : Count_Type
      ) return Element_Type;

   generic
      Result : access Index_Type := null;
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Search_N (
      Source : Array_Type;
      Count : Count_Type;
      Value : Element_Type
      ) return Boolean;

   generic
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   procedure Generic_Replace (
      Source : in out Array_Type;
      Old_Value : Element_Type;
      New_Value : Element_Type
      );

   generic
      with function Predicate (Item : Element_Type) return Boolean;
   procedure Generic_Replace_If (
      Source : in out Array_Type;
      New_Value : Element_Type
      );


   generic
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Replace_Copy (
      Source : Array_Type;
      Old_Value : Element_Type;
      New_Value : Element_Type
      ) return Array_Type;

   generic
      with function Predicate (Item : Element_Type) return Boolean;
   function Generic_Replace_Copy_If (
      Source : Array_Type;
      New_Value : Element_Type
      ) return Array_Type;

   generic
      with function Generator return Element_Type;
   procedure Generic_Generate (Source : in out Array_Type);

   generic
      with function Generator return Element_Type;
   procedure Generic_Generate_N (
      Source : in out Array_Type;
      N : Count_Type
      );

   generic
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   procedure Generic_Remove (
      Source : in out Array_Type;
      Value : Element_Type;
      Last : out Index_Type'Base
      );

   generic
      with function Predicate (Item : Element_Type) return Boolean;
   procedure Generic_Remove_If (
      Source : in out Array_Type;
      Last : out Index_Type'Base
      );

   generic
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Remove_Copy (
      Source : Array_Type;
      Value : Element_Type
      ) return Array_Type;

   generic
      with function Predicate (Item : Element_Type) return Boolean;
   function Generic_Remove_Copy_If (
      Source : Array_Type
      ) return Array_Type;

   generic
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   procedure Generic_Unique (
      Source : in out Array_Type;
      Last : out Index_Type
      );

   generic
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Unique_Copy (
      Source : Array_Type
      ) return Array_Type;

   procedure Reverse_Array (Source : in out Array_Type);

   function Reverse_Copy (Source : Array_Type) return Array_Type;

   procedure Rotate (
      Source : in out Array_Type;
      M : Index_Type
      )
      with Pre => M in Source'Range;
   -- after rotate, index M become Source'First

   function Rotate_Copy (
      Source : Array_Type;
      M : Index_Type
      ) return Array_Type;

   generic
      with function "<" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Max_Index (
      Source : Array_Type
      ) return Index_Type;

   generic
      with function "<" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Min_Index (
      Source : Array_Type
      ) return Index_Type;

   generic
      with function "<" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Max_Element (
      Source : Array_Type
      ) return Element_Type
      with Pre => Source'Length > 0;

   generic
      with function "<" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Min_Element (
      Source : Array_Type
      ) return Element_Type
      with Pre => Source'Length > 0;

   procedure Random_Shuffle (Source : in out Array_Type);

   generic
      with function Predicate (Item : Element_Type) return Boolean;
   procedure Generic_Partition (
      Source : in out Array_Type;
      Right_Start : out Index_Type
      );

   generic
      with function "+" (X, Y : Element_Type) return Element_Type is <>;
   function Generic_Sum (
      Source : Array_Type
      ) return Element_Type
      with Pre => Source'Length > 0;

   generic
      with function "*" (X, Y : Element_Type) return Element_Type is <>;
   function Generic_Dot_Product (
      A : Array_Type;
      B : Array_Type
      ) return Array_Type
      with Pre => A'Length > 0 and then (A'Length = B'Length); 

   generic
      with function "+" (X, Y : Element_Type) return Element_Type is <>;
      with function "*" (X, Y : Element_Type) return Element_Type is <>;
   function Generic_Inner_Product (
      A : Array_Type;
      B : Array_Type
      ) return Element_Type
      with Pre => A'Length = B'Length;

   generic
      with function Binary_Operation (X, Y : Element_Type) return Element_Type;
   function Generic_Accumulate (
      Source : Array_Type
      ) return Element_Type
      with Pre => Source'Length > 0;

   generic
      with function "<" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Is_Sorted (
      Source : Array_Type
      ) return Boolean;

   function Starts_With (
      Source : Array_Type;
      Pattern : Array_Type
      ) return Boolean;

   function Ends_With (
      Source : Array_Type;
      Pattern : Array_Type
      ) return Boolean;

   generic
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   function Generic_Count (
      Source : Array_Type;
      Pattern : Array_Type
      ) return Count_Type;

   generic
      with function Continue (
         Piece : Array_Type;
         Index : Positive; 
         Done  : Boolean
         ) return Boolean;
      with function "=" (X, Y : Element_Type) return Boolean is <>;
   procedure Generic_Split (
      Source    : Array_Type;
      Delimiter : Array_Type
      );

   generic
      Source : Array_Type;
      Delimiter : Array_Type;
   package Generic_Split_Reader is
      -- usages : 
      -- Reset;
      -- while Next loop
      --    Process (piece);
      -- end loop;

      procedure Reset;

      function Next return Boolean;

      function Piece return Array_Type with Inline;

   end Generic_Split_Reader;

   generic
      Source : Array_Type;
      Delimiter : Array_Type;
   package Generic_Split_Pair is

      function Split return Boolean with Inline;

      function Head return Array_Type with Inline;

      function Tail return Array_Type with Inline; 

   end Generic_Split_Pair;

   generic
      Delimiter : Array_Type;
   function Generic_Join (
      Source1, Source2 : Array_Type
      ) return Array_Type;

   generic
      with function "<" (X, Y : Element_Type) return Boolean is <>;
   procedure Generic_Merge (
      Sorted_1 : Array_Type;
      Sorted_2 : Array_Type;
      Result   : in out Array_Type
      ) with Pre => Result'Length = Sorted_1'Length + Sorted_2'Length; 

   type Replace_Flag is (rf_All, rf_Translate);
   type Replace_Flags is array(Replace_Flag) of Boolean with Pack;

   function To_Flags (S : String) return Replace_Flags
      with Pre => S'Length = 0 or else (for all c of S => c in 'g' | 'G' | 'i' | 'I');

   generic
      with procedure Translate (Source : in out Array_Type);
   function Generic_Replace_Slice (
      Source : Array_Type;
      Pattern : Array_Type;
      Value : Array_Type;
      Flags : Replace_Flags := (others => False)
      ) return Array_Type;
   -- example :
   -- function Replace is new Generic_Replace_Slice(To_Upper);
   -- S : String := Replace("hellO, world", "o", "k", To_Flags("gi"));
   -- OK : Boolean := S = "hellk, wkrld";


   generic
      Data : in out Array_Type;
   package Generic_Append is

      procedure Reset;

      function Is_Empty return Boolean with Inline;

      function Length return Count_Type;

      procedure Append (X : Array_Type);

      procedure Append (X : Element_Type);

      function View return Array_Type with Inline;

   end Generic_Append;

   type Pointer is access Array_Type;

   procedure Free is new Ada.Unchecked_Deallocation(Array_Type, Pointer);

   generic
      with procedure Destroy (Item : in out Element_Type);
   procedure Generic_Destroy (P : in out Pointer);

end Generic_Array;
